#pragma once
#include "../Graphics/Graphics.h"
#include "../Color/Color.h"
#include "../Window/UIWindow.h"
#include <vector>
#include <thread>
#include "../Input/Keyboard.hpp"

#define TEXT_ALLIGNMENT_CENTER 1
#define TEXT_ALLIGNMENT_LEFT 2
#define TEXT_ALLIGNMENT_RIGHT 3

struct Point
{
	float x, y;
};

class UIElement
{
public:
	// Draws the element on the screen and has to be called between graphics.BeginDraw() and graphics.EndDraw()
	virtual void __stdcall Draw(Graphics* graphics) = 0;

	UIWindow* SourceWindow = nullptr;

	bool Visible = true;
	Color color = Color::LightGray;
	float Width = 20;
	float Height = 20;
	Point Position = Point{ 40, 40 };
	int DepthIndex = 0;

	// Returns the enabled state of the element
	virtual bool IsEnabled() { return this->enabled; }

	// Sets enabled state of the element
	virtual void SetEnabled(bool state)
	{
		this->enabled = state;
		if (state) { this->color.a = normalAlpha; } // reset alpha component back to original
		else { this->color.a = 100; }				// make the whole element darker by lowering the alpha component
	}

	// Returns whether the element is in focus
	virtual bool IsFocused() { return this->inFocus; }

	// Focuses the element
	virtual void Focus();

	// Unfocuses the element
	virtual void Unfocus();

	// Callback function prototype
	using callback_function = std::function<void(UIElement*)>;
	using keybd_callback_function = std::function<void(UIElement*, KeyEvt)>;
	using scrollwhl_callback_function = std::function<void(UIElement*, int)>;

	/// <summary> Fades the element out over given period of time in milliseconds </summary>
	/// <param name="time"> duration of the animation in milliseconds </param>
	virtual void __stdcall FadeOut(int time)
	{
		std::thread fadeout_thread([this, &time]
		{
			Color currentColor = color;
			int currentAlpha = currentColor.a;
			if (currentAlpha == 0) { currentAlpha = 1; }
			int sleepIntervals = time / currentAlpha;
			for (int i = currentAlpha; i > 0; i--)
			{
				currentColor.a--;
				this->color = currentColor;
				Sleep(sleepIntervals);
			}
			this->Visible = false;
		});
		fadeout_thread.detach();
	}

	/// <summary> Fades the element in over given period of time in milliseconds until the needed alpha value is reached </summary>
	/// <param name="time"> duration of the animation in milliseconds </param>
	/// <param name="finalAlpha"> duration of the animation in milliseconds </param>
	virtual void __stdcall FadeIn(int time, int finalAlpha = 255)
	{
		int sleepInterval = 0;
		if (finalAlpha < color.a) return; // invalid alpha state
		int delta = finalAlpha - color.a;
		if (delta == 0)
		{
			sleepInterval = time;
		}
		else
		{
			sleepInterval = time / delta;
		}

		if (finalAlpha >= 255)
			finalAlpha = 254;

		std::thread fadein_thread([this, sleepInterval, finalAlpha] {
			this->Visible = true;
			for (int i = color.a; i <= finalAlpha; i++)
			{
				Sleep(sleepInterval);
				uint8_t val = (uint8_t)i;
				color.a = val;
				this->normalAlpha = color.a;
			}
		});
		fadein_thread.detach();
	}

	// Adds a callback function for left mouse click event
	virtual void AddLeftMouseClickEvent(callback_function event) { this->leftMouseClickEvents.push_back(event); }

	// Returns callback functions for left mouse click event
	virtual std::vector<callback_function>& GetLeftMouseClickEvents() { return this->leftMouseClickEvents; }

	// Adds a callback function for right mouse click event
	virtual void AddRightMouseClickEvent(callback_function event) { this->rightMouseClickEvents.push_back(event); }

	// Returns callback functions for right mouse click event
	virtual std::vector<callback_function>& GetRightMouseClickEvents() { return this->rightMouseClickEvents; }

	// Adds a callback function for mouse hover on event
	virtual void AddMouseHoverOnEvent(callback_function event) { this->mouseHoverOnEvents.push_back(event); }

	// Returns callback functions for mouse hover on event
	virtual std::vector<callback_function>& GetMouseHoverOnEvents() { return this->mouseHoverOnEvents; }

	// Adds a callback function for mouse hover off event
	virtual void AddMouseHoverOffEvent(callback_function event) { this->mouseHoverOffEvents.push_back(event); }

	// Returns callback functions for mouse hover off event
	virtual std::vector<callback_function>& GetMouseHoverOffEvents() { return this->mouseHoverOffEvents; }

	// Adds a callback function for mouse down event
	virtual void AddMouseDownEvent(callback_function event) { this->mouseDownEvents.push_back(event); }

	// Returns callback functions for mouse down event
	virtual std::vector<callback_function>& GetMouseDownEvents() { return this->mouseDownEvents; }

	// Adds a callback function for when the element comes into focus
	virtual void AddEnterFocusEvent(callback_function event) { this->enterFocusEvents.push_back(event); }

	// Returns callback functions for when the element comes into focus
	virtual std::vector<callback_function>& GetEnterFocusEvents() { return this->enterFocusEvents; }

	// Adds a callback function for when the element leaves the focus
	virtual void AddLeaveFocusEvent(callback_function event) { this->leaveFocusEvents.push_back(event); }

	// Returns callback functions for when the element leaves the focus
	virtual std::vector<callback_function>& GetLeaveFocusEvents() { return this->leaveFocusEvents; }

	// Adds a callback function for key down event
	virtual void AddKeyEvent(keybd_callback_function event) { this->keypressEvents.push_back(event); }

	// Returns callback functions for key down event
	virtual std::vector<keybd_callback_function>& GetKeyEvents() { return this->keypressEvents; }

	// Adds a callback function for mouse scroll wheel event
	virtual void AddScrollWheelEvent(scrollwhl_callback_function event) { this->scrollWheelEvents.push_back(event); }

	// Returns callback functions for mouse scroll wheel event
	virtual std::vector<scrollwhl_callback_function>& GetScrollWheelEvents() { return this->scrollWheelEvents; }

	// Public virtual destructor
	virtual ~UIElement() {  }

protected:
	UIElement() { /* default constructor */ }
	UIElement(float x, float y) { this->Position.x = x; this->Position.y = y; }
	UIElement(float x, float y, float width, float height) { this->Position.x = x; this->Position.y = y; this->Width = width; this->Height = height; }
	UIElement(float x, float y, float width, float height, Color color) { this->Position.x = x; this->Position.y = y; this->Width = width; this->Height = height; this->color = color; this->normalAlpha = color.a; }

	bool enabled = true;
	bool inFocus = false;
	int normalAlpha = 255; // max alpha

	// list of callback events
	std::vector<callback_function> leftMouseClickEvents;
	std::vector<callback_function> rightMouseClickEvents;
	std::vector<callback_function> mouseHoverOnEvents;
	std::vector<callback_function> mouseHoverOffEvents;
	std::vector<callback_function> mouseDownEvents;
	std::vector<callback_function> enterFocusEvents;
	std::vector<callback_function> leaveFocusEvents;
	std::vector<keybd_callback_function> keypressEvents;
	std::vector<scrollwhl_callback_function> scrollWheelEvents;

	// Creates proper d2d1 text allignment options
	void MakeTextAllignment(int allignment, DWRITE_TEXT_ALIGNMENT& textAllignment, DWRITE_PARAGRAPH_ALIGNMENT& paragraphAllignment)
	{
		if (allignment == TEXT_ALLIGNMENT_CENTER)
		{
			textAllignment = DWRITE_TEXT_ALIGNMENT_CENTER;
			paragraphAllignment = DWRITE_PARAGRAPH_ALIGNMENT_CENTER;
		}
		else if (allignment == TEXT_ALLIGNMENT_LEFT)
		{
			textAllignment = DWRITE_TEXT_ALIGNMENT_LEADING;
			paragraphAllignment = DWRITE_PARAGRAPH_ALIGNMENT_NEAR;
		}
		else if (allignment == TEXT_ALLIGNMENT_RIGHT)
		{
			textAllignment = DWRITE_TEXT_ALIGNMENT_TRAILING;
			paragraphAllignment = DWRITE_PARAGRAPH_ALIGNMENT_FAR;
		}
		else
		{
			textAllignment = DWRITE_TEXT_ALIGNMENT_CENTER;
			paragraphAllignment = DWRITE_PARAGRAPH_ALIGNMENT_CENTER;
		}
	}
};
